package fclog

import (
	"fmt"
	"log"
	"os"
	"path"
	"runtime"
	"sync"
	"time"

	"gitee.com/fcsvr/fancy/utils"
)

type Logger struct {
	buf         []byte
	lvbuf       []byte
	name        string
	srcStr      string
	curLv       Level
	lv          Level
	outputMode  int
	logStdChan  chan string
	logFileChan chan string
	lock        sync.Mutex

	stdLogger *log.Logger

	logFlag      int
	fileLogger   *log.Logger
	filePtr      *os.File
	fileWriter   *LogFile
	fileIdx      int
	fileSplit    string
	fileInterval int
	fileSize     int
}

var logger *Logger

func init() {
	var err error
	logger, err = NewLogger(utils.G_Data.Name, LV_TRACE, LOG_OUTPUT_SF)
	if err != nil {
		panic(err)
	}
	//go logger.goStdLog()
	//go logger.goFileLog()
}

func NewLogger(name string, lv Level, mode int) (*Logger, error) {
	l := &Logger{
		name:         name,
		buf:          make([]byte, 0),
		lvbuf:        make([]byte, 0),
		lv:           lv,
		logFlag:      log.Lshortfile | log.LstdFlags | log.Lmicroseconds,
		outputMode:   mode,
		fileInterval: LOG_INTERVAL_DAY,
		fileSize:     LOG_DEF_MAX_SIZE,
		logStdChan:   make(chan string, 100),
		logFileChan:  make(chan string, 100),
	}
	if err := l.InitConsole(); err != nil {
		return l, err
	}
	if err := l.InitFile(); err != nil {
		return l, err
	}
	return l, nil
}

func (l *Logger) InitConsole() error {
	l.stdLogger = log.New(os.Stderr, "", l.logFlag)
	return nil
}

func (l *Logger) InitFile() error {
	var err error
	l.fileSplit = l.getSplitTag(time.Now())
	l.filePtr, err = l.findFile(-1)
	if err != nil {
		return err
	}
	l.fileWriter = NewLogFile(l.filePtr)
	l.fileLogger = log.New(l.fileWriter, "", l.logFlag)

	go l.fileCheck()
	return nil
}

func (l *Logger) fileCheck() {
	for {
		time.Sleep(time.Second * 1)
		curTm := time.Now()
		newSplit := l.getSplitTag(curTm)
		if newSplit == l.fileSplit {
			st, err := l.filePtr.Stat()
			if err != nil {
				l.Default("logger file get stat fail err = %v\n", err)
				continue
			}
			fsize := st.Size()
			//l.Info("fileCheck size = %d, filesize = %d", fsize, l.fileSize)
			if fsize > int64(l.fileSize) {
				if l.fileIdx == LOG_MAX_FILE_IDX {
					l.Default("logger file num max = %d\n", l.fileIdx)
					continue
				} else {
					l.fileIdx++
					l.Default("new logger file idx = %d\n", l.fileIdx)
				}
			} else {
				continue
			}
		} else {
			l.fileSplit = newSplit
			l.fileIdx = 0
		}

		newFilePtr, err := l.findFile(l.fileIdx)
		if err != nil {
			l.Default("new logger file idx = %d, spilt = %v err\n", l.fileIdx, l.fileSplit)
			continue
		}
		l.filePtr = newFilePtr
		l.fileWriter.NextFile(l.filePtr)
		l.Default("new logger file\n")
	}
}

func (l *Logger) getSplitTag(tm time.Time) string {
	if l.fileInterval == LOG_INTERVAL_DAY {
		return tm.Format("0102")
	} else if l.fileInterval == LOG_INTERVAL_HOUR {
		return tm.Format("010215")
	} else if l.fileInterval == LOG_INTERVAL_MIN {
		return tm.Format("01021504")
	}
	return tm.Format("0102")
}

func (l *Logger) findFile(idx int) (*os.File, error) {
	findIdx := idx
	if idx < 0 {
		for i := LOG_MAX_FILE_IDX; i >= 1; i-- {
			fileName := fmt.Sprintf("%v.%v.%02d.log", l.name, l.fileSplit, i)
			filePath := path.Join(utils.G_Data.LogDir, fileName)
			filePtr, err := os.OpenFile(filePath, os.O_RDONLY, os.ModePerm)
			if err != nil {
				continue
			}
			findIdx = i
			filePtr.Close()
			break
		}
		if findIdx == -1 {
			findIdx = 0
		}
	}
	fileName := fmt.Sprintf("%v.%v.%02d.log", l.name, l.fileSplit, findIdx)
	filePath := path.Join(utils.G_Data.LogDir, fileName)
	filePtr, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
	if err != nil {
		return nil, err
	}
	l.fileIdx = findIdx
	return filePtr, nil
}

func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
	if l.logFlag&(log.Ldate|log.Ltime|log.Lmicroseconds) != 0 {
		if l.logFlag&log.LUTC != 0 {
			t = t.UTC()
		}
		if l.logFlag&log.Ldate != 0 {
			year, month, day := t.Date()
			fcitoa(buf, year, 4)
			*buf = append(*buf, '/')
			fcitoa(buf, int(month), 2)
			*buf = append(*buf, '/')
			fcitoa(buf, day, 2)
			*buf = append(*buf, ' ')
		}
		if l.logFlag&(log.Ltime|log.Lmicroseconds) != 0 {
			hour, min, sec := t.Clock()
			fcitoa(buf, hour, 2)
			*buf = append(*buf, ':')
			fcitoa(buf, min, 2)
			*buf = append(*buf, ':')
			fcitoa(buf, sec, 2)
			if l.logFlag&log.Lmicroseconds != 0 {
				*buf = append(*buf, '.')
				fcitoa(buf, t.Nanosecond()/1e3, 6)
			}
			*buf = append(*buf, ' ')
		}
	}
	if l.logFlag&(log.Lshortfile|log.Llongfile) != 0 {
		if l.logFlag&log.Lshortfile != 0 {
			short := file
			for i := len(file) - 1; i > 0; i-- {
				if file[i] == '/' {
					short = file[i+1:]
					break
				}
			}
			file = short
		}
		*buf = append(*buf, file...)
		*buf = append(*buf, ':')
		fcitoa(buf, line, -1)
		*buf = append(*buf, ": "...)
	}
}

func (l *Logger) fmtStr(lv Level, buf *[]byte, str string) {
	now := time.Now()
	var file string
	var line int
	if l.logFlag&(log.Lshortfile|log.Llongfile) != 0 {
		var ok bool
		_, file, line, ok = runtime.Caller(3)
		if !ok {
			file = "???"
			line = 0
		}
	}
	*buf = append(*buf, getColorPrefixByLv(lv)...)
	l.formatHeader(buf, now, file, line)

	*buf = append(*buf, getFlagStrByLv(lv)...)
	*buf = append(*buf, str...)
	if len(str) == 0 || str[len(str)-1] != '\n' {
		*buf = append(*buf, '\n')
	}
	*buf = append(*buf, logColorSuffix...)
}

func (l *Logger) Log(lv Level, str string) {
	if lv < l.lv {
		return
	}
	l.lock.Lock()
	defer l.lock.Unlock()

	buf := make([]byte, 0, 0)
	l.fmtStr(lv, &buf, str)

	l.lvbuf = l.lvbuf[:0]

	if l.checkOutputMode(LOG_OUTPUT_STD) {
		os.Stderr.Write(buf)
	}
	if l.checkOutputMode(LOG_OUTPUT_FILE) {
		buf = buf[6:]
		buf = buf[0 : len(buf)-4]
		l.fileWriter.Write(buf)
	}
}

func (l *Logger) checkOutputMode(m int) bool {
	return (l.outputMode & m) > 0
}

func (l *Logger) Trace(str string, v ...interface{}) {
	l.Log(LV_TRACE, fmt.Sprintf(str, v...))
}

func (l *Logger) Debug(str string, v ...interface{}) {
	l.Log(LV_DEBUG, fmt.Sprintf(str, v...))
}

func Debug(str string, v ...interface{}) {
	logger.Log(LV_DEBUG, fmt.Sprintf(str, v...))
}

func (l *Logger) Info(str string, v ...interface{}) {
	l.Log(LV_INFO, fmt.Sprintf(str, v...))
}

func Info(str string, v ...interface{}) {
	logger.Log(LV_INFO, fmt.Sprintf(str, v...))
}

func (l *Logger) Warn(str string, v ...interface{}) {
	l.Log(LV_WARN, fmt.Sprintf(str, v...))
}
func Warn(str string, v ...interface{}) {
	logger.Log(LV_WARN, fmt.Sprintf(str, v...))
}

func (l *Logger) Err(str string, v ...interface{}) {
	l.Log(LV_ERROR, fmt.Sprintf(str, v...))
}

func Error(str string, v ...interface{}) {
	logger.Log(LV_ERROR, fmt.Sprintf(str, v...))
}

func (l *Logger) Fatal(str string, v ...interface{}) {
	l.Log(LV_FATAL, fmt.Sprintf(str, v...))
}

func Fatal(str string, v ...interface{}) {
	logger.Log(LV_FATAL, fmt.Sprintf(str, v...))
	os.Exit(1)
}

func (l *Logger) Default(str string, v ...interface{}) {
	fmt.Printf(str, v...)
}

func Sys(str string, v ...interface{}) {
	fmt.Printf(str, v...)
}
func Sysln(v ...interface{}) {
	fmt.Println(v...)
}

func fcitoa(buf *[]byte, i int, wid int) {
	// Assemble decimal in reverse order.
	var b [20]byte
	bp := len(b) - 1
	for i >= 10 || wid > 1 {
		wid--
		q := i / 10
		b[bp] = byte('0' + i - q*10)
		bp--
		i = q
	}
	// i < 10
	b[bp] = byte('0' + i)
	*buf = append(*buf, b[bp:]...)
}
